import { clone } from './ts_utils'

const normalizeLabelName = (label: string) => label.toUpperCase()
const BREAK_KEY = 'break__';


const descendantsCount = (items: any[], id: string) => {
  return items.reduce((acc, item) => {
    if (item.parentId === id) {
      return acc + 1 + descendantsCount(items, item.id)
    }
    return acc
  }, 0)
}

// 展开增加并添加end
export const flattenTreeForRun = (treeData: any) => {
  const items: any = []
  const flatten = (list: any, parentId: any = null, depth: any = 0) => {
    list.forEach(({ children, ...item }: any) => {

      item.id = item.id ?? window.crypto.randomUUID();
      items.push({
        ...item,
        parentId,
        depth,
        seq: items.length + 1
      })

      if (/^(do|while|forEach|times|if|elseif|else)$/.test(item.cmd)) {
        flatten(children || [], item.id, depth + 1);
        items.push({
          id: window.crypto.randomUUID(),
          cmd: 'end',
          value: '',
          target: '',
          parentId,
          depth,
          seq: -1 // 页面看不到设置为seq
        })
      }
    })
  }
  flatten(treeData, null, 0)
  console.log(items, 'items')
  return items
}

export default class Interpreter {
  static DefaultState: any = {
    labels: {},
    tags: [],
    commands: [],
    // Any data specific to any command, for example, `times` and `forEach` uses it to store loop cursor
    extra: {}
  }

  state: {
    labels: any;
    tags: any[];
    commands: any[];
    extra: {
      break__?: any[], // 断点
      [key: string]: any
    }
  } = clone(Interpreter.DefaultState)

  __customPre?: (c: any, i: number) => any
  __customRun?: (command: any, index: number) => any;
  __customPost?: (command: any, index: number, result: any) => any

  constructor(opts: {
    pre?: () => any;
    run?: (command: any, index: number) => any;
    post?: () => any;
  } = {}) {
    if (opts.pre) {
      this.__customPre = opts.pre
    }

    if (opts.run) {
      this.__customRun = opts.run
    }

    if (opts.post) {
      this.__customPost = opts.post
    }
  }

  reset() {
    this.__setState({
      labels: {},
      tags: [],
      commands: []
    })
  }

  // 预处理,把标签和执行顺序记录下来
  // 给if while times forEach添加虚拟的end 
  // 执行结束根据结果去找虚拟end,再根据虚拟end找到nextIndex
  preprocess(commands: any[]) {
    console.log(commands, 'commands解析预处理')
    let nextState: any = { commands, tags: [] }
    let halfTags: any = []
    let errorAtIndex = (i: any, msg: any) => {
      const e: any = new Error(msg)
      e.errorIndex = i
      return e
    }

    commands.forEach((c: any, i: number) => {
      if (this.__customPre && this.__customPre(c, i)) return

      const topHalfTag = halfTags[halfTags.length - 1]

      switch (c.cmd) {
        // Commands for if, while, do, times, forEach statements 
        case 'if': {
          const brothers = commands.filter((item: any) => item.parentId === c.parentId)
          const startIndex = brothers.findIndex((item: any) => item.id === c.id)
          const children: any[] = [];
          for (let i = startIndex + 1; i < brothers.length; i++) {
            if (brothers[i].cmd === 'end') {
              continue;
            }
            if (brothers[i].cmd === 'elseif' || brothers[i].cmd === 'else') {
              const index = commands.findIndex((item: any) => item.id === brothers[i].id)
              children.push({ index, type: brothers[i].cmd })
            } else {
              break;
            }
          }
          halfTags.push({
            type: c.cmd,
            start: { index: i, command: c },
            children
          })
          break
        }
        case 'elseif':
        case 'else':
        case 'times':
        case 'forEach':
        case 'while':
        case 'do': {
          halfTags.push({
            type: c.cmd,
            start: { index: i, command: c }
          })
          break
        }
        case 'repeatIf': {
          if (!topHalfTag || !/^do$/.test(topHalfTag.type)) {
            throw errorAtIndex(i, `No matching do for this repeatIf (at command #${i + 1})`)
          }

          nextState.tags.push({
            ...topHalfTag,
            end: { index: i, command: c }
          })

          halfTags.pop()
          break
        }
        case 'break': {
          let targetHalfTag
          for (let j = halfTags.length - 1; j >= 0; j--) {
            if (halfTags[j].start.index < i && /^(do|while|forEach|times)$/.test(halfTags[j].type)) {
              targetHalfTag = halfTags[j];
              break;
            }
          }

          if (!targetHalfTag) {
            throw errorAtIndex(i, `No matching loop command for this ${c.cmd} (at command #${i + 1})`)
          }

          targetHalfTag.children = targetHalfTag.children || []
          targetHalfTag.children.push({ index: i, command: c })

          break;
        }
        case 'continue': {

          // find from bottom last tag having index less than current index
          let lastTag = null;
          for (let j = halfTags.length - 1; j >= 0; j--) {
            if (halfTags[j].start.index < i && /^(do|while|forEach|times)$/.test(halfTags[j].type)) {
              lastTag = halfTags[j];
              break;
            }
          }

          // console.log('continue:>> lastTag: ', lastTag);
          let targetHalfTag = lastTag;

          if (!targetHalfTag) {
            throw errorAtIndex(i, `No matching loop command for this ${c.cmd} (at command #${i + 1})`)
          }
          targetHalfTag.children = targetHalfTag.children || []
          targetHalfTag.children.push({ index: i, command: c })
          break;
        }
        case 'end':
          {
            const [reg, text] = [/^(if|elseif|else|while|times|forEach)$/, 'if/elseif/else/while/times/forEach']

            if (!topHalfTag || !reg.test(topHalfTag.type)) {
              throw errorAtIndex(i, `No matching ${text} for this end (at command #${i + 1})`)
            }

            nextState.tags.push({
              ...topHalfTag,
              end: { index: i, command: c }
            })

            halfTags.pop()
            break
          }
        case 'label': {
          if (!c.target || !c.target.length) {
            throw new Error('invalid target for label command')
          }

          this.__setState({
            labels: {
              ...this.state.labels,
              [normalizeLabelName(c.target)]: { index: i }
            }
          })

          break
        }
      }
    })
    this.__setState(nextState)
  }


  run(command: any, index: number) {
    const { cmd, target } = command

    const p = (() => {
      switch (cmd) {
        case 'onError': {
          const value = command.value && command.value.trim()
          const target = command.target && command.target.trim()
          const isValidTarget = target && (/^#restart$/i.test(target) || /^#goto$/i.test(target))

          if (!isValidTarget) {
            throw new Error('invalid target for onError command')
          }

          if (/^#goto$/i.test(target)) {
            const labelName = normalizeLabelName(value)

            if (!this.state.labels[labelName]) {
              throw new Error(`label ${value} doesn't exist`)
            }
          }

          return Promise.resolve({ isFlowLogic: true })
        }

        case 'gotoLabel': {
          if (!target || !target.length) {
            throw new Error('invalid target for gotoLabel command')
          }

          const labelName = normalizeLabelName(target)

          if (!this.state.labels[labelName]) {
            throw new Error(`label ${target} doesn't exist`)
          }

          return Promise.resolve({
            isFlowLogic: true,
            nextIndex: this.state.labels[labelName].index
          })
        }

        case 'elseif': {
          const tag = this.state.tags.find(tag => tag.start.index === index && tag.type === cmd)

          if (!tag) {
            throw new Error(`tag not found for this elseif (at command #${index + 1})`)
          }

          // Note: if the `if` tag has already tried some branch, then this `elseif` should act like `else`
          // otherwise it acts like `if`

          return Promise.resolve({ isFlowLogic: false })

        }

        case 'else': {
          // Note: 'else' and 'elseif' command itself will be skipped if condition is false,
          // But it will be run as the ending command of 'if-else' when condition is true
          const tag = this.state.tags.find(tag => tag.start.index === index && tag.type === cmd)

          if (!tag) {
            throw new Error(`tag not found for this else (at command #${index + 1})`)
          }

          return Promise.resolve({
            isFlowLogic: true,
            nextIndex: tag.end.index + 1
          })
        }

        case 'break': {
          const tag = this.state.tags.find(tag => {
            return /^(do|while|forEach|times)$/.test(tag.type) &&
              tag.children &&
              tag.children.find((item: any) => item.index === index && item.command.cmd === cmd)
          })

          if (!tag) {
            throw new Error(`No loop found for this break (at command #${index + 1})`)
          }

          this.setExtraByKey('times_1', 0);
          this.addBreak({
            command: command, // for debugging purpose
            targetTagStartIndex: tag.start.index,
          });

          return Promise.resolve({
            isFlowLogic: true,
            nextIndex: tag.end.index + 1
          })
        }

        case 'continue': {
          const tag = this.state.tags.find(tag => {
            return /^(do|while|forEach|times)$/.test(tag.type) &&
              tag.children &&
              tag.children.find((item: any) => item.index === index && item.command.cmd === cmd)
          })

          if (!tag) {
            throw new Error(`No loop found for this break (at command #${index + 1})`)
          }

          return Promise.resolve({
            isFlowLogic: true,
            nextIndex: tag.start.index
          })
        }

        case 'end': {
          const tag = this.state.tags.find(tag => /^(if|elseif|else|while|times|forEach)$/.test(tag.type) && tag.end.index === index)

          if (!tag) {
            throw new Error(`tag not found for this end (at command #${index + 1})`)
          }

          if (/^if$/.test(tag.type)) {
            const [lastChild] = tag.children.slice(-1)
            if (lastChild) {
              // if后有elseif或者else
              const lastTag = this.state.tags.find(tag => tag.start.index === lastChild.index && tag.type === lastChild.type)
              return Promise.resolve({ isFlowLogic: true, nextIndex: lastTag.end.index + 1 })
            }
            // if后没有elseif或者else
            return Promise.resolve({ isFlowLogic: true })
          }

          if (/^elseif$/.test(tag.type)) {
            // 找到if标签
            const ifTag = this.state.tags.find(t => t.type === 'if' && t.children.find((item: any) => item.index === tag.index && item.type === tag.type))
            if (ifTag) {
              // if后有elseif或者else
              const [lastChild] = ifTag.children.slice(-1)
              if (lastChild) {
                const lastTag = this.state.tags.find(tag => tag.start.index === lastChild.index && tag.type === lastChild.type)
                return Promise.resolve({ isFlowLogic: true, nextIndex: lastTag.end.index + 1 })
              }
              // if后没有elseif或者else
              return Promise.resolve({ isFlowLogic: true })
            } else {
              // 没有if标签
              throw new Error(`tag not found if for this ${cmd} (at command #${index + 1})`)
            }
          }

          if (/^else$/.test(tag.type)) {
            return Promise.resolve({ isFlowLogic: true })
          }

          // Then it's a `while`, `forEach`, `times`
          return Promise.resolve({
            isFlowLogic: true,
            nextIndex: tag.start.index
          })
        }

        case 'comment':
        case 'label':
        case 'do':
          return Promise.resolve({ isFlowLogic: true })

        default:
          return Promise.resolve({ isFlowLogic: false })
      }
    })()

    return p.then(result => {
      if (result.isFlowLogic) {
        return result
      }

      if (this.__customRun) {
        const p = this.__customRun(command, index)
        if (p) return Promise.resolve(p)
      }

      return result
    })
  }

  postRun(command: any, index: number, result: any) {
    const { cmd, value } = command

    if (this.__customPost) {
      const p = this.__customPost(command, index, result)
      if (p) return Promise.resolve(p)
    }

    switch (cmd) {

      case 'gotoIf': {
        // short-circuit the check on value
        if (!result.condition) return Promise.resolve()

        if (!value || !value.length) {
          throw new Error('invalid value for value command')
        }

        const labelName = normalizeLabelName(value)

        if (!this.state.labels[labelName]) {
          throw new Error(`label ${value} doesn't exist`)
        }

        return Promise.resolve({
          nextIndex: this.state.labels[labelName].index
        })
      }
      case 'if': {
        const cond = result.condition;
        const tag = this.state.tags.find(tag => tag.start.index === index && tag.type === cmd)
        if (!tag) {
          throw new Error(`'if' tag not found for this ${cmd} (at command #${index + 1})`)
        }

        const [lastChild] = tag.children.slice(-1)
        if (lastChild) {
          // 如果if后有elseif或者else
          const lastTag = this.state.tags.find(tag => tag.start.index === lastChild.index && tag.type === lastChild.type)
          return Promise.resolve({
            nextIndex: cond ? (index + 1) : lastTag.end.index + 1
          })
        } else {
          // 如果if后没有elseif和else
          return Promise.resolve({
            nextIndex: cond ? (index + 1) : tag.end.index + 1
          })
        }
      }
      case 'elseif': {
        // 成功以后后面的elseif和else不执行
        // 先找到elseif前的if
        // 再找到if后面的最后一个elseif或者else
        const cond = result.condition;// 条件
        const tag = this.state.tags.find(tag => tag.type === 'if' && tag.children.find((item: any) => item.index === index))
        if (!tag) {
          throw new Error(`'elseif' tag not found for this ${cmd} (at command #${index + 1})`)
        }
        const [lastChild] = tag.children.slice(-1)
        const lastTag = this.state.tags.find(tag => tag.start.index === lastChild.index && tag.type === lastChild.type)
        return Promise.resolve({
          nextIndex: cond ? (index + 1) : lastTag.end.index + 1
        })
      }
      case 'times':
      case 'forEach':
      case 'while': {
        const cond = result.condition;
        const tag = this.state.tags.find(tag => tag.start.index === index && tag.type === cmd)
        if (!tag) {
          throw new Error(`tag not found for this ${cmd} (at command #${index + 1})`)
        }

        if (!tag.end || tag.end.index === undefined || tag.end.index === null) {
          throw new Error(`tag doesn't have a valid end index`)
        }

        return Promise.resolve(
          cond ? {} : { nextIndex: tag.end.index + 1 }
        )
      }

      case 'repeatIf': {
        const cond = result.condition
        const tag = this.state.tags.find(tag => /^do$/.test(tag.type) && tag.end.index === index)

        if (!tag) {
          throw new Error(`tag not found for this repeatIf (at command #${index + 1})`)
        }

        if (!tag.end || tag.start.index === undefined || tag.start.index === null) {
          throw new Error(`tag doesn't have a valid start index`)
        }

        return Promise.resolve(
          cond ? { nextIndex: tag.start.index + 1 } : {}
        )
      }

      default:
        return Promise.resolve()
    }
  }

  commandIndexByLabel(labelName: any) {
    const label = this.state.labels[normalizeLabelName(labelName)]

    if (!label) {
      throw new Error(`label '${labelName}' doesn't exist`)
    }

    return label.index
  }

  backupState() {
    return clone(this.state)
  }

  restoreState(state: any) {
    this.__setState(state)
  }

  getKeyForTimes(index: any) {
    return `times_${index}`
  }

  getKeyForSurroundingTimes(timesCommandIndex: any) {
    const tagIndex = this.state.tags.findIndex((tag: any) => /^(times)$/.test(tag.type) && tag.start.index === timesCommandIndex)

    if (tagIndex === -1) {
      return null
    }

    const currentTimesTag = this.state.tags[tagIndex]
    const surroundingTimesTag = (() => {
      for (let i = tagIndex; i < this.state.tags.length; i++) {
        const tag = this.state.tags[i]

        if (tag.type === 'times' &&
          tag.start.index < currentTimesTag.start.index &&
          tag.end.index > currentTimesTag.end.index
        ) {
          return tag
        }
      }

      return null
    })()

    if (!surroundingTimesTag) {
      return null
    }

    return this.getKeyForTimes(surroundingTimesTag.start.index)
  }

  getExtraByKey(key: string): any {
    return this.state.extra[key]
  }

  setExtraByKey(key: string, value: any) {
    this.state.extra[key] = value
  }

  getBreaks(BREAK_KEY: string) {
    return this.state.extra[BREAK_KEY]
  }

  addBreak(value: any) {
    let existingBreaks = this.getBreaks(BREAK_KEY);
    this.setExtraByKey(BREAK_KEY, existingBreaks ? [...existingBreaks, value] : [value]);
  }

  removeBreak(targetTagStartIndex: any) {
    let existingBreaks = this.getBreaks(BREAK_KEY) || [];
    let newValue = existingBreaks.filter((item: any) => {
      return item.targetTagStartIndex !== targetTagStartIndex;
    });
    this.setExtraByKey(BREAK_KEY, newValue);
  }

  hasBreak(targetTagStartIndex: any) {
    let existingBreaks = this.getBreaks(BREAK_KEY) || [];
    let isExist = existingBreaks.some((item: any) => {
      return item.targetTagStartIndex === targetTagStartIndex;
    });
    return isExist;
  }

  updateExtraByKey(key: any, updater: (key: any) => any) {
    const value = this.getExtraByKey(key)
    this.setExtraByKey(key, updater(value))
  }

  removeExtraByKey(key: any) {
    delete this.state.extra[key]
  }

  __setState(st: any) {
    this.state = {
      ...this.state,
      ...st
    }
  }
}


